Skip to content

useState 函数式更新

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

jsx
const [state, setState] = useState({});
setState((prevState) => {
  // 也可以使用 Object.assign
  return { ...prevState, ...updatedValues };
});

引用类型 state 更新

经常 state 数据不是简单数据类型(值类型),而是数组、对象之类(引用类型).而 React 组件的更新机制对 state 只进行浅对比,也就是更新某个复杂类型数据时只要它的引用地址没变,那就不会重新渲染组件。

尤其当被更新的引用类型数据需要依赖之前的数据时,容易踩坑,不触发更新。更新复杂 state 的时候必须传给它一个全新的对象,而不是复制了它引用地址再修改的对象。

两种解决办法:

  1. 结合展开运算符返回一个新对象

    jsx
    function nextValue(preValue) {
      let ipValue = Number.parseInt(newVal, 10);
    
      if (Number.isNaN(ipValue)) {
        ipValue = undefined;
      }
      if (ipValue < 0) {
        ipValue = 0;
      }
      if (ipValue > 255) {
        ipValue = 255;
      }
      preValue[index] = ipValue; //这里数据引用地址的值同样被修改
      return [...preValue]; // 数据最外层的引用地址不同即可触发组件更新
    }
    
    setValue((preValue) => nextValue(preValue));
  2. 深拷贝对象,用全新的副本更新数据。

    jsx
    function nextValue(preValue) {
      const newValue = _.cloneDeep(preValue);
      let ipValue = Number.parseInt(newVal, 10);
    
      if (Number.isNaN(ipValue)) {
        ipValue = undefined;
      }
      if (ipValue < 0) {
        ipValue = 0;
      }
      if (ipValue > 255) {
        ipValue = 255;
      }
      newValue[index] = ipValue; //这里数据引用地址的值同样被修改
      return newValue; // 数据最外层的引用地址不同即可触发组件更新
    }
    
    setValue((preValue) => nextValue(preValue));

## 纠错与补充

- React 只比较 `setState` 传入值的引用是否变化,即便对象内部字段不同也不会触发重新渲染;因此“修改完再 `setState(obj)`”这种模式一定失效。
- 深拷贝可以用 `structuredClone`、`JSON.parse(JSON.stringify(obj))` 或 `lodash.cloneDeep`,但最推荐的还是 `immer`:`setState(prev => produce(prev, draft => { draft[index] = ipValue }))`,性能和可维护性都更好。
- 如果 state 结构很复杂,考虑拆分为多个 `useState` 或使用 `useReducer`;后者的 reducer 始终接收上一份 state,可避免层层展开。

Copyright ©2025 moweiwei